package rollinglog_demo

import (
	"fmt"
	"io"
	"log"
	"os"
	"os/signal"
	"syscall"

	"github.com/robfig/cron/v3"

	_ "github.com/robfig/cron/v3"
	"gopkg.in/natefinch/lumberjack.v2"
)

type RollingLogger struct {
	*log.Logger
	rollingOutput *lumberjack.Logger
	scheduler     *cron.Cron

	daily  chan struct{}
	stop   chan struct{}
	sighup chan os.Signal
}

func NewLogger(extraOutput io.Writer, prefix string, flag int, opts ...Option) *RollingLogger {
	rollingOutput := &lumberjack.Logger{
		// 日志输出文件路径，默认os.TempDir()
		//Filename: os.TempDir(),
		// 日志文件最大 size, 单位是 MB，默认100MB
		//MaxSize: 100,
		// 保留过期文件的最大时间间隔,单位是天，默认不过期
		//MaxAge: 0,
		// 最大过期日志保留的个数，默认全部保留，若设置了MaxAge，优先以MaxAge为准
		//MaxBackups: 0,
		// 是否需要压缩滚动日志, 使用的 gzip 压缩，默认不压缩
		//Compress: false,
		// 是否使用时间戳命名 backup 日志, 默认使用 UTC 格式，默认不启用
		//LocalTime: false,
	}
	var output io.Writer
	if extraOutput != nil {
		output = io.MultiWriter(os.Stderr, rollingOutput)
	} else {
		output = rollingOutput
	}

	logger := log.New(output, prefix, flag)
	sighup := make(chan os.Signal, 1)
	//通过系统信号分割
	signal.Notify(sighup, syscall.SIGHUP)
	rollingLogger := &RollingLogger{
		Logger:        logger,
		rollingOutput: rollingOutput,
		scheduler:     cron.New(cron.WithSeconds()),
		daily:         make(chan struct{}),
		stop:          make(chan struct{}),
		sighup:        sighup,
	}
	for _, opt := range opts {
		opt(rollingLogger)
	}
	rollingLogger.startRotate()
	return rollingLogger
}

func (l *RollingLogger) startRotate() {
	//每天零点定时滚动日志
	l.scheduler.AddFunc("0 0 0 * * ?", l.daliyRote)
	//开启定时任务
	l.scheduler.Start()
	go func() {
		defer func() {
			if err := recover(); err != nil {
				fmt.Fprintf(os.Stderr, "rollingLogger exit with panic(%v)", err)
			}
			if l.scheduler != nil {
				//关闭定时任务
				l.scheduler.Stop()
			}
		}()

		for {
			select {
			case <-l.daily:
				//每日切割
				l.doRotate()
			case <-l.sighup:
				//通过信号切割
				l.doRotate()
			case <-l.stop:
				return
			}
		}
	}()
}

func (l *RollingLogger) daliyRote() {
	l.daily <- struct{}{}
}

func (l *RollingLogger) doRotate() {
	err := l.rollingOutput.Rotate()
	if err != nil {
		fmt.Fprintf(os.Stderr, "rollingLogger doRotate error: %v", err)
	}
}

func (l *RollingLogger) Stop() {
	l.stop <- struct{}{}
}
